package com.scvtc.service.system.impl;

import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.scvtc.domain.ResResult;
import com.scvtc.domain.dto.common.SystemLoginUser;
import com.scvtc.domain.dto.common.SystemUserLoginDto;
import com.scvtc.domain.dto.system.user.UserInfoDto;
import com.scvtc.domain.dto.system.user.UserRoleDto;
import com.scvtc.domain.entity.system.SystemUser;
import com.scvtc.domain.entity.system.SystemUserRole;
import com.scvtc.domain.vo.PageVo;
import com.scvtc.domain.vo.system.user.UserListVo;
import com.scvtc.mapper.system.SystemUserMapper;
import com.scvtc.service.system.SystemUserRoleService;
import com.scvtc.service.system.SystemUserService;
import com.scvtc.utils.BeanCopyUtils;
import com.scvtc.utils.JwtUtil;
import com.scvtc.utils.RedisUtil;
import com.scvtc.utils.SecurityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.scvtc.constants.RedisConstant.USER_LOGIN;
import static com.scvtc.domain.enums.AppHttpCodeEnum.*;

/**
 * 用户管理(SystemUser)表服务实现类
 *
 * @author yiFei
 * @since 2022-04-13 17:38:29
 */
@Service("systemUserService")
public class SystemUserServiceImpl extends ServiceImpl<SystemUserMapper, SystemUser> implements SystemUserService {

    @Autowired
    private SystemUserRoleService systemUserRoleService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtUtil jwtUtil;

    @Value("${jwt.token-header}")
    private String tokenHeader;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private RedisUtil redisUtil;

    @Value("${system.default.password}")
    private String defaultPassword;

    /**
     * 用户登录
     *
     * @param systemUser
     * @return
     */
    @Override
    public ResResult<Map<String, Object>> login(SystemUserLoginDto systemUser) {
        // 进行 登录 认证
        Authentication authenticate = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(systemUser.getUsername(), systemUser.getPassword())
        );
        // 如是空 就报密码错误
        Assert.notNull(authenticate, BAD_CREDENTIALS.getMsg());
        // 写入 redis
        SystemLoginUser loginUser = (SystemLoginUser) authenticate.getPrincipal();
        // 将用户输入的密码存入 redis ('安全易患')
        // TODO 使用加密算法加密 JasyptEncryptorUtil 再存入 ( 建议盐为用户名 )
        loginUser.getUserInfo().setPassword(systemUser.getPassword());
        // system:user:login::1 登录的用户
        redisUtil.setCacheObject(USER_LOGIN.concat(loginUser.getUserInfo().getId().toString()), loginUser, 30, TimeUnit.MINUTES);
        // 获取用户基本信息
        UserInfoDto userInfoDto = BeanCopyUtils.copyBean(loginUser.getUserInfo(), UserInfoDto.class);
        // 隐藏密码
        userInfoDto.setPassword(null);
        // 获取 token
        String token = jwtUtil.creatJwtToken(tokenHeader, loginUser.getUserInfo().getId());
        // 构造前端需要的数据
        return ResResult.customize(
                LOGIN_SUCCESS,
                MapUtil.builder(new HashMap<String, Object>())
                        .put("userInfo", userInfoDto)
                        .put("token", token)
                        .build()
        );
    }

    /**
     * 获取用户详情信息
     *
     * @return
     */
    @Override
    public ResResult<SystemLoginUser> userInfo() {
        // 获取当前用户
        SystemLoginUser systemLoginUser = SecurityUtil.getSystemLoginUser();
        systemLoginUser.getUserInfo().setPassword(null);
        return ResResult.noMsgSuccess(systemLoginUser);
    }

    /**
     * 用户登出
     *
     * @return
     */
    @Override
    public ResResult<String> logout() {
        // 这样登出只包含一个用户
//        Long userId = SecurityUtil.getUserId();
//        redisUtil.deleteObject(USER_LOGIN.concat(userId.toString()));
        return ResResult.customize(LOGOUT_SUCCESS);
    }

    /**
     * 获取所有用户
     *
     * @param current
     * @param size
     * @param userInfo
     * @return
     */
    @Override
    public ResResult<PageVo<List<UserListVo>>> getAllUser(Long current, Long size, UserInfoDto userInfo) {
        // 获取所有用户
        Page<SystemUser> page = getBaseMapper().pageUserRole(new Page<SystemUser>(current, size), SecurityUtil.getUserId(), userInfo);
        // 封装用户信息
        List<SystemUser> records = page.getRecords();
        List<UserListVo> userListVoList = BeanCopyUtils.copyBean(records, UserListVo.class);
        Long total = page.getTotal();
        // 查询是否在线
        for (UserListVo userListVo : userListVoList) {
            // 如果 redis 中存在
            if (redisUtil.getCacheObject(USER_LOGIN.concat(userListVo.getId().toString())) != null) {
                userListVo.setOnline(true);
            } else {
                userListVo.setOnline(false);
            }
        }
        return ResResult.noMsgSuccess(new PageVo<>(userListVoList, total));
    }

    /**
     * 增加用户
     *
     * @param userRoleDto
     * @return
     */
    @Override
    public ResResult<?> addUser(UserRoleDto userRoleDto) {
        // 添加角色
        SystemUser systemUser = BeanCopyUtils.copyBean(userRoleDto, SystemUser.class).setId(null);
        // 给用户密码加密
        systemUser.setPassword(passwordEncoder.encode(systemUser.getPassword()));
        if (save(systemUser)) {
            // 封装用户的角色信息
            List<SystemUserRole> collect;
            if (!CollectionUtils.isEmpty(userRoleDto.getChildren())) {
                collect = userRoleDto.getChildren().stream()
                        .distinct()
                        .map(item -> new SystemUserRole(systemUser.getId(), item.getId()))
                        .collect(Collectors.toList());
            } else {
                // 如果用户传入id等于空就删除 (一般不会出现这种错误)
                removeById(systemUser);
                return ResResult.customize(MISSING_USER_ROLE);
            }
            if (systemUserRoleService.saveBatch(collect)) {
                return ResResult.success();
            }
        }
        return ResResult.fail();
    }

    /**
     * 修改用户信息
     *
     * @param userInfoDto
     * @return
     */
    @Override
    public ResResult<?> updateUser(UserInfoDto userInfoDto) {
        SystemUser systemUser = BeanCopyUtils.copyBean(userInfoDto, SystemUser.class);
        // 给用户密码加密
        systemUser.setPassword(passwordEncoder.encode(systemUser.getPassword()));
        if (updateById(systemUser)) {
            // 刷新redis
            refreshLoginUser(userInfoDto.getId());
            return ResResult.success();
        }
        return ResResult.fail();
    }


    /**
     * 删除用户
     *
     * @param id
     * @return
     */
    @Override
    public ResResult<?> deleteUser(Long id) {
        // 统一返回
        if (removeById(id)) {
            // 删除缓存数据
            redisUtil.deleteObject(USER_LOGIN.concat(id.toString()));
            return ResResult.success();
        }
        return ResResult.fail();
    }

    /**
     * 重置用户密码
     *
     * @param id
     * @return
     */
    @Override
    public ResResult<?> initUserPassword(Long id) {
        // 设置默认密码
        String encode = passwordEncoder.encode(defaultPassword);
        // 修改密码为 默认密码
        if (update(
                new LambdaUpdateWrapper<SystemUser>()
                        .eq(SystemUser::getId, id)
                        .set(SystemUser::getPassword, encode)
        )) {
            return ResResult.success();
        }
        return ResResult.fail();
    }

    /**
     * 强制下线
     *
     * @param id
     * @return
     */
    @Override
    public ResResult<?> offline(Long id) {
        redisUtil.deleteObject(USER_LOGIN.concat(id.toString()));
        return ResResult.success();
    }

    /**
     * 修改用户角色信息
     *
     * @param userId
     * @param roleIds
     * @return
     */
    @Transactional
    @Override
    public ResResult<?> updateUserRoleIdsDto(Long userId, List<Long> roleIds) {
        if (CollectionUtils.isEmpty(roleIds)) {
            return ResResult.customize(MISSING_USER_ROLE);
        }
        // 如果删除报错就回滚
        systemUserRoleService.remove(
                new LambdaQueryWrapper<SystemUserRole>()
                        .eq(SystemUserRole::getUserId, userId)
        );
        // 封装成  SystemUserRole 来添加多个
        List<SystemUserRole> collect = roleIds.stream()
                .distinct()
                .map(roleId -> new SystemUserRole(userId, roleId))
                .collect(Collectors.toList());
        // 删除成功直接返回
        if (systemUserRoleService.saveBatch(collect)) {
            // 刷新用户
            refreshLoginUser(userId);
            // 返回 userId 是为了方便前端判断
            return ResResult.success(userId);
        }
        // 如果删除失败 ， 手动回滚事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return ResResult.fail();
    }

    @Override
    public ResResult<?> enableUser(Long id, String status) {
        // 如果修改成功，返回成功
        if (update(
                new LambdaUpdateWrapper<SystemUser>()
                        .eq(SystemUser::getId, id)
                        .set(SystemUser::getStatus, status)
        )) {
            return ResResult.success();
        }
        return ResResult.fail();
    }


    /**
     * 刷新用户(更新当前用户的角色)
     *
     * @param id
     */
    @Async("taskExecutor")
    public void refreshLoginUser(@NotNull Long id) {
        SystemLoginUser systemLoginUser = redisUtil.getCacheObject(USER_LOGIN.concat(id.toString()));
        if (!ObjectUtils.isEmpty(systemLoginUser)) {
            String username = systemLoginUser.getUsername();
            String password = systemLoginUser.getPassword();
            // 自动让用户重新登录
            login(new SystemUserLoginDto(username, password));
        }
    }
}

